package com.ddpt.permission.util;

import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.ClassPathResource;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.Key;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.util.HashMap;
import java.util.Map;

/**
 * JWT用公钥证书验签工具类
 * @author zhangsh
 * @data 2021年4月13日
 */
@Slf4j
public class JWTJKSUtilDecoder {

	// 本类唯一实例
	private static JWTJKSUtilDecoder _instance = null;
	// 同步控制锁
	private Object lock = new Object();
	// 默认私钥证书文件
	private String keyStorePath = "./jwt/";
	// 缓存证书列表
	private static Map<String, Key> publicKeyMap = new HashMap<>();


	/**
	 * 获取本类唯一实例
	 * @return
	 * @author zhangsh
	 * @data 2021年4月13日
	 */
	public static JWTJKSUtilDecoder getInstance() {
		synchronized (JWTJKSUtilDecoder.class) {
			if (_instance == null) {
				synchronized (JWTJKSUtilDecoder.class) {
					_instance = new JWTJKSUtilDecoder();
				}
			}
			return _instance;
		}
	}
	
	/**
	 * 按别名找证书
	 * @param alias
	 * @return
	 * @author zhangsh
	 * @data 2021年4月13日
	 */
	private Key getPublicKey(String alias) {
		Key key = null;
		
		// 如果加载过证书文件就不再读取文件
		synchronized (this.lock) {
			if (!publicKeyMap.containsKey(alias)) {
				synchronized (this.lock) {
					String filePath = keyStorePath + alias + ".cer";
					
					if(System.getProperty("os.name").toLowerCase().contains("windows")) {
						filePath = System.getProperty("user.dir") +  
								File.separator + "src" + 
								File.separator + "main" + 
								File.separator + "resources" +
								File.separator + "public" + 
								File.separator + alias + ".cer";
						log.info("window平台证书路径:{}", filePath);
					}
					
					InputStream is = null;
					try {
						// File file = new File(filePath);
						File file = new ClassPathResource(filePath).getFile();
						if(!file.exists()) {
							log.error("不存在{}证书文件 ", filePath);
							throw new RuntimeException("未找到 " + filePath + "证书文件");
						}

						is = new FileInputStream(file);		
						CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
						Certificate certificate = certificateFactory.generateCertificate(is);
						key = certificate.getPublicKey();
						
						publicKeyMap.put(alias, key);
					} catch (CertificateException | IOException e1) {
						log.error("加载" + filePath +"证书文件失败 ", e1);
						throw new RuntimeException("加载 " + filePath + "证书文件失败", e1);
					}finally {
						if (is != null) {
							try {
								is.close();
							} catch (Exception e) {
								log.warn("关闭 " + filePath + "输入流时发生错误", e);
							}
						}
					}
				}
			}else {
				key = publicKeyMap.get(alias);
			}
		}
		return key;
	}


	private Key getPublicKey(String url,String alias)  {

		Key key = null;

		synchronized (this.lock) {
			if (!publicKeyMap.containsKey(alias)) {
				HttpResponse execute = HttpRequest.post(url).execute();
				InputStream inputStream = execute.bodyStream();
				CertificateFactory certificateFactory = null;
				try {
					certificateFactory = CertificateFactory.getInstance("X.509");
					Certificate certificate = certificateFactory.generateCertificate(inputStream);
					key = certificate.getPublicKey();
					publicKeyMap.put(alias, key);
				} catch (CertificateException e) {
					log.error("加载" + url + "证书文件失败 ", e);
					throw new RuntimeException("加载 " + url + "证书文件失败", e);
				}finally {
					if (inputStream != null) {
						try {
							inputStream.close();
						} catch (Exception e) {
							log.warn("关闭 " + alias + "输入流时发生错误", e);
						}
					}
				}
			}else {
				key =publicKeyMap.get(alias);
			}
		}
		return  key;
	}


	/**
	 * 
	 * @param alias
	 * @param token
	 * @param hash
	 * @return
	 * @author zhangsh
	 * @data 2021年4月13日
	 */
	public boolean signature(String alias, String token, String hash) {
		boolean pass = false;
		try {
           Claims claims = Jwts.parser()
        		   .setSigningKey(getPublicKey(alias))
        		   .parseClaimsJws(token).getBody();
           String subject = claims.getSubject();
           // 校验主题域
           pass = this.checkSubject(subject, hash);
        } catch (ExpiredJwtException e) {
        	log.error("验签失败, Token过期", e);
        }catch(SignatureException e) {
        	log.error("验签失败", e);
        }
		return pass;
	}

	/**
	 *
	 * @param alias
	 * @param token
	 * @param hash
	 * @return
	 * @author zhangsh
	 * @data 2021年4月13日
	 */
	public boolean signature(String alias, String token, String hash,String keyUrl) {
		boolean pass = false;
		try {
			Claims claims = Jwts.parser()
					.setSigningKey(getPublicKey(keyUrl,alias))
					.parseClaimsJws(token).getBody();
			String subject = claims.getSubject();
			// 校验主题域
	        pass = this.checkSubject(subject, hash);
		} catch (ExpiredJwtException e) {
			log.error("验签失败, Token过期", e);
		}catch(SignatureException e) {
			log.error("验签失败", e);
		}
		
		return pass;
	}

	/**
	 * 验证主题域
	 * @param subject
	 * @param hash
	 * @return
	 * @author zhangsh
	 * @data 2023年4月11日
	 */
	private boolean checkSubject(String subject, String hash) {
		boolean pass = false;
		/**
         * 主题内容不为空，验证下主题域
         * 特别说明：设计主题域格式 {"hash":"md5值"}，实现的时候没有写成json格式， 只有md5值
         * 接口已经生效了，就变成只有md5了
         */
        if (StringUtils.isNotBlank(subject)){
     	   pass = subject.equals(hash);
     	   if(!pass) {
     		   log.warn("主题验证失败, 预期值：{}，实际值：{}", hash, subject);
     	   }
		}else {
			pass = true;		// 只要解析成功，就是验证通过了
		}
        
        return pass;
	}

	public static void main(String[] args) {
		JWTJKSUtilDecoder instance = JWTJKSUtilDecoder.getInstance();
		boolean signature = instance.signature("testbp.cathaylife.cn",
				"eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJDYXRoYXkiLCJzdWIiOiJkNmE5YTkzM2M4YWFmYzUxZTU1YWMwNjYyYjZlNGQ0YSIsImlhdCI6MTcwNjA2MDMwMH0." +
						"ZXNW08oHokzbQe7-xW9tt06FbS5n_Jm747jcRPrNp-bjBed3B1yYNkDVs9Sj9iZJuphb55mk2yGF8nduto8YN6A8uk-2UwHFMc7nboAVnCZQNnHLh0iTwrxjFfXi5DI4eY-" +
						"u1dSyBcvphuUrkW6Tf8tfz4InwUkygjArG5fySko3bew-3ei7-6O8T5QZ934MPdeSOygOJQ_iHLPpCs-YWZIB49upTyc85JFdTLEWwmro140_uiF0vPqcYO9L6YVfBL-rbYEvW1Y16RkeYem6Ef5-" +
						"nK239bbm7QqQpfm2-Povw8deWSyGB-dtZVY_WIOqLmx1VsIpylzPXy1kQneU9A", "d6a9a933c8aafc51e55ac0662b6e4d4a");
		System.out.println(signature);
	}
}
